﻿using Inovout.Component;
using Inovout.Data.Domain;
using Inovout.Data.Repository;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;

namespace Inovout.Web.Mvc
{
    public class EntityModelBinder : DefaultModelBinder
    {
        private readonly FieldInfo valueFieldOfNmeValueCollectionValueProvider;
        public EntityModelBinder()
        {
            valueFieldOfNmeValueCollectionValueProvider =
                typeof(NameValueCollectionValueProvider).GetField("_values", BindingFlags.Instance | BindingFlags.NonPublic);
        }
        private const string idKey = "Id";

        protected bool TryGetFiledValue(ModelBindingContext bindingContext, string propertyName, out object fieldValue)
        {
            fieldValue = null;
            string fieldName = !string.IsNullOrEmpty(propertyName) ? string.Format("{0}.{1}", propertyName, idKey) : CreateSubPropertyName(bindingContext.ModelName, idKey);
            var fieldValueProviderResult = bindingContext.ValueProvider.GetValue(fieldName);
            if (fieldValueProviderResult == null || string.IsNullOrEmpty(fieldValueProviderResult.AttemptedValue) ||
                fieldValueProviderResult.AttemptedValue.Equals("undefined")//新增时，没有Id隐藏域字段
                )
            {
                return false;
            }

            DataObjectTypeDescriptor dataObjectTypeDescriptor = DataObjectTypeDescriptor.Get(bindingContext.ModelType);
            fieldValue = fieldValueProviderResult.ConvertTo(dataObjectTypeDescriptor.GetDataObjectProperty(idKey).MappingType);
            return true;
        }

        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var result = base.BindModel(controllerContext, bindingContext);
            if (!bindingContext.ModelState.IsValid)
            {
                var validationResult = bindingContext.ModelState.First(ms => ms.Value.Errors.Any()).Value.Errors.First();
                ComponentRegistry.Resolve<TransactionTracker>().ExceptionContext.Exceptions = new Exception[] { new ArgumentException(validationResult.ErrorMessage) };
            }
            return result;
        }
        private void RewriteNameValueCollection(IEnumerable<NameValueCollectionValueProvider> nvcValueProviders, string fullPropertyName)
        {
            foreach (var valueProvider in nvcValueProviders)
            {
                IDictionary values = (IDictionary)valueFieldOfNmeValueCollectionValueProvider.GetValue(valueProvider);

                string source = fullPropertyName;
                if (values.Contains(source))
                {
                    string target = fullPropertyName + ".Id";
                    values.Add(target, values[source]);
                    values.Remove(source);
                }
            }
        }

        private static readonly QueryCriteriaModelBinder queryCriteriaModelBinder = new QueryCriteriaModelBinder();
        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            return CreateDataObjectModel(bindingContext, modelType) ??
                CreateQueryCriteriaModel(controllerContext, bindingContext, modelType) ??
                 CreateComponentRegistryModel(bindingContext, modelType) ??
                base.CreateModel(controllerContext, bindingContext, modelType);

        }
        private object CreateQueryCriteriaModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            if (controllerContext.HttpContext.Request.HttpMethod.ToLower().Equals("get"))
            {
                if (typeof(QueryCriteria).IsAssignableFrom(bindingContext.ModelType))
                {
                    return queryCriteriaModelBinder.BindModel(controllerContext, bindingContext);
                }
                else if (typeof(FilterCriteria[]).IsAssignableFrom(bindingContext.ModelType))
                {
                    return queryCriteriaModelBinder.BindFilters(controllerContext, bindingContext);

                }
                else if (typeof(OrderCriteria[]).IsAssignableFrom(bindingContext.ModelType))
                {
                    return queryCriteriaModelBinder.BindOrders(controllerContext, bindingContext);

                }


            }
            return null;
        }
        private object CreateComponentRegistryModel(ModelBindingContext bindingContext, Type modelType)
        {
            //只支持主对象，若遇到没有任何对应的属性值，由Action自行处理。
            if (string.IsNullOrEmpty(bindingContext.ModelName) &&
                (modelType.IsInterface || modelType.IsAbstract) && ComponentRegistry.IsRegistered(modelType))
            {
                return ComponentRegistry.Resolve(modelType);
            }
            return null;
        }
        private object CreateDataObjectModel(ModelBindingContext bindingContext, Type modelType, string propertyName = null)
        {
            object idValue;
            if (typeof(DataObject).IsAssignableFrom(modelType) &&
                TryGetFiledValue(bindingContext, propertyName ?? bindingContext.ModelName, out idValue))
            {
                Type respositoryType = typeof(IRepository<>);
                respositoryType = respositoryType.MakeGenericType(modelType);

                IRepository repository = ComponentRegistry.Resolve<IRepository>(respositoryType);
                return repository.FindById(idValue);
            }

            return null;
        }

        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
        {
            if (typeof(DataObject).IsAssignableFrom(propertyDescriptor.PropertyType))
            {
                var request = controllerContext.HttpContext.Request;
                string fullPropertyName = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
                if (request.QueryString.AllKeys.Contains(fullPropertyName) || request.Form.AllKeys.Contains(fullPropertyName))
                {
                    RewriteNameValueCollection(
                        ((ValueProviderCollection)bindingContext.ValueProvider).OfType<NameValueCollectionValueProvider>(), fullPropertyName);
                }
                object dbProperty =CreateDataObjectModel(bindingContext,propertyDescriptor.PropertyType,CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name));
                if (dbProperty != null)
                {
                    propertyDescriptor.SetValue(bindingContext.Model, dbProperty);
                }
            }
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }
    }
}

